import { JsonOb } from './JsonOb';

import { _decorator, Component, Node, v2, Vec2, game, log, director } from 'cc';
import { DEBUG, EDITOR } from 'cc/env';
const { ccclass, property } = _decorator;
const VM_EMIT_HEAD = 'VC:';
const DEBUG_SHOW_PATH = false;

//判断是否是对象
function isObject(obj: any): boolean {
    return obj && typeof obj === 'object';
}
//通过 .  路径 设置值
function setValueFromPath(obj: any, path: string, value: any, tag: string = '') {
    let props = path.split('.');
    for (let i = 0; i < props.length; i++) {
        const propName = props[i];
        if (propName in obj === false) { console.error('[' + propName + '] not find in ' + tag + '.' + path); break; }
        if (i == props.length - 1) {
            if(isObject(value)){
                for (const key in value) {
                    obj[propName][key] = value[key];
                }
            }else
            obj[propName] = value;
        } else {
            obj = obj[propName];
        }
    }

}

//通过 . 路径 获取值
function getValueFromPath(obj: any, path: string, def?: any, tag: string = ''): any {
    let props = path.split('.');
    for (let i = 0; i < props.length; i++) {
        const propName = props[i];
        if ((propName in obj === false)) { console.error('[' + propName + '] not find in ' + tag + '.' + path); return def; }
        obj = obj[propName];
    }
    if (obj === null || typeof obj === "undefined") obj = def;//如果g == null 则返回一个默认值
    return obj;

}



/**
 * ModelViewer 类
 */
class ViewModel<T>{
    constructor(data: T, tag: string) {
        new JsonOb(data, this._callback.bind(this));
        this.$data = data;
        this._tag = tag;
    }

    public $data: T;

    //索引值用的标签
    private _tag: string = null;

    /**激活状态, 将会通过 director.emit 发送值变动的信号, 适合需要屏蔽的情况 */
    public active: boolean = true;

    /**是否激活根路径回调通知, 不激活的情况下 只能监听末端路径值来判断是否变化 */
    public emitToRootPath: boolean = false;

    //回调函数 请注意 回调的 path 数组是 引用类型，禁止修改
    private _callback(n: any, o: any, path: string[]): void {
        if (this.active == true) {
            let name = VM_EMIT_HEAD + this._tag + '.' + path.join('.')
            if (DEBUG_SHOW_PATH) log('>>', n, o, path);
            director.emit(name, n, o, [this._tag].concat(path)); //通知末端路径

            if (this.emitToRootPath) director.emit(VM_EMIT_HEAD + this._tag, n, o, path);//通知主路径

            if (path.length >= 2) {
                for (let i = 0; i < path.length - 1; i++) {
                    const e = path[i];
                    //log('中端路径');

                }
            }

            if (this._tag === "local") {
                let local = window.localStorage;
                let t = path.shift();
                if (path.length > 0) {
                    let data = JSON.parse(local.getItem(t));
                    data[path.join(".")] = n;
                    local.setItem(t, JSON.stringify(data));
                } else {
                    local.setItem(t, JSON.stringify(n))
                }
                path.unshift(t);
            }

        }
    }
    private isObject(value) {
        return value === Object(value);
    }
    //通过路径设置数据的方法
    public setValue(path: string, value: any) {

        // if (this._tag === "local") {
        //     let local=window.localStorage;
        //     let t=value.shift();
        //     if(value.length>0){
        //         let data=JSON.parse(local.getItem(t));
        //         data[value.join(".")]=n;
        //         local.setItem(t,JSON.stringify(data));
        //     }else{
        //         local.setItem(t,JSON.stringify(n))
        //     }
        // }

        if (this.isObject(value)) {
            value = JSON.parse(JSON.stringify(value));
            // value=Object.create(value);
        }

        setValueFromPath(this.$data, path, value, this._tag);
    }
    //获取路径的值
    public getValue(path: string, def?: any): any {
        return getValueFromPath(this.$data, path, def, this._tag);
    }
}

/**
 * VM 对象管理器(工厂)
 */
class VMManager {
    /**静态数组，保存创建的 mv 组件 */
    private _mvs: Array<{ tag: string, vm: ViewModel<any> }> = [];

    private EMIT_HEAD = VM_EMIT_HEAD;


    /**
     * 绑定一个数据，并且可以由VM所管理
     * @param data 需要绑定的数据
     * @param tag 对应该数据的标签(用于识别为哪个VM，不允许重复)
     * @param activeRootObject 激活主路径通知，可能会有性能影响，一般不使用
     */
    add<T>(data: T, tag: string = 'global', activeRootObject: boolean = false) {
        let vm = new ViewModel<T>(data, tag);
        let has = this._mvs.find(v => v.tag === tag);
        if (tag.includes('.')) {
            console.error('cant write . in tag:', tag);
            return;
        }
        if (has) {
            console.error('already set VM tag:' + tag);
            return;
        }

        vm.emitToRootPath = activeRootObject;
        this._mvs.push({ tag: tag, vm: vm });

    }

    /**
     * 移除并且销毁 VM 对象
     * @param tag 
     */
    remove(tag: string) {
        let index = this._mvs.findIndex(v => v.tag === tag);
        if (index >= 0) this._mvs.splice(index, 1);
    }

    /**
     * 获取绑定的数据
     * @param tag 数据tag
     */
    get<T>(tag: string): ViewModel<T> {
        let res = this._mvs.find(v => v.tag === tag);
        if (res == null) {
            console.error('cant find VM from:', tag);
        } else {
            return res.vm;
        }
    }

    /**
     * 通过全局路径,而不是 VM 对象来 设置值
     * @param path - 全局取值路径
     * @param value - 需要增加的值
     */
    addValue(path: string, value: any) {
        path = path.trim();//防止空格,自动剔除
        let rs = path.split('.');
        if (rs.length < 2) { console.error('Cant find path:' + path) };
        let vm = this.get(rs[0]);
        if (!vm) { console.error('Cant Set VM:' + rs[0]); return; };
        let resPath = rs.slice(1).join('.');
        vm.setValue(resPath, vm.getValue(resPath) + value);
        return vm.getValue(resPath)
    }

    /**
     * 通过全局路径,而不是 VM 对象来 获取值
     * @param path - 全局取值路径
     * @param def - 如果取不到值的返回的默认值
     */
    getValue(path: string, def?: any): any {
        path = path.trim();//防止空格,自动剔除
        let rs = path.split('.');
        if (rs.length < 2) { console.error('Get Value Cant find path:' + path); return; };
        let vm = this.get(rs[0]);
        if (!vm) { console.error('Cant Get VM:' + rs[0]); return; };
        return vm.getValue(rs.slice(1).join('.'), def);
    }

    /**
     * 通过全局路径,而不是 VM 对象来 设置值
     * @param path - 全局取值路径
     * @param value - 需要设置的值
     */
    setValue(path: string, value: any) {
        path = path.trim();//防止空格,自动剔除
        let rs = path.split('.');
        if (rs.length < 2) { console.error('Set Value Cant find path:' + path); return; };
        let vm = this.get(rs[0]);
        if (!vm) { console.error('Cant Set VM:' + rs[0]); return; };
        vm.setValue(rs.slice(1).join('.'), value);
    }

    setObjValue = setValueFromPath;
    getObjValue = getValueFromPath;

    /**等同于 director.on */
    bindPath(path: string, callback: any, target?: any, useCapture?: boolean): void {
        path = path.trim();//防止空格,自动剔除
        if (path == '') {
            console.error(target.node.name, '节点绑定的路径为空');
            return;
        }
        if (path.split('.')[0] === '*') {
            console.error(path, '路径不合法,可能错误覆盖了 VMParent 的onLoad 方法, 或者父节点并未挂载 VMParent 相关的组件脚本');
            return;
        }
        director.on(VM_EMIT_HEAD + path, callback, target, useCapture);
    }

    /**等同于 director.off */
    unbindPath(path: string, callback: any, target?: any): void {
        path = path.trim();//防止空格,自动剔除
        if (path.split('.')[0] === '*') {
            console.error(path, '路径不合法,可能错误覆盖了 VMParent 的onLoad 方法, 或者父节点并未挂载 VMParent 相关的组件脚本');
            return;
        }
        director.off(VM_EMIT_HEAD + path, callback, target);
    }


    /**冻结所有标签的 VM，视图将不会受到任何信息 */
    inactive(): void {
        this._mvs.forEach(mv => {
            mv.vm.active = false;
        })
    }

    /**激活所有标签的 VM*/
    active(): void {
        this._mvs.forEach(mv => {
            mv.vm.active = true;
        })
    }



}

//   整数、小数、时间、缩写

/**
 * VM管理对象,使用文档: 
 *  https://github.com/wsssheep/cocos_creator_mvvm_tools/blob/master/docs/ViewModelScript.md
 */
export let VM = new VMManager();